﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.UI;

using AjaxControlFramework.Reflection;

namespace AjaxControlFramework
{
	public class ControlCallbackStrategy : CallbackStrategy
	{
        //------// Fields \\------------------------------------------------\\
        protected static Type ScriptControlManagerType = Type.GetType(Properties.Settings.Default.ScriptControlManager_TypeName, true);
        //------\\ Fields //------------------------------------------------//



		//------// Constructors \\------------------------------------------\\
		public ControlCallbackStrategy(Control control)
			: base(control)
		{ }
		//------\\ Constructors //------------------------------------------//



		//------// Methods \\-----------------------------------------------\\
        public override void LoadControl(CallbackStrategyArguments callbackArguments)
        {
            TargetControl = this.Control as Control;
        }


        public override AjaxControlContext GenerateControlContext(CallbackStrategyArguments callbackArguments)
        {
            AjaxControlContext newContext = new AjaxControlContext
            {
                CallbackArguments = callbackArguments,
                State = callbackArguments.State,
                Html = String.Empty,
                Control = TargetControl // *Reminder* - The TargetControl property is set during the Page.LoadCompleted event, before Page.PrepareCallback is called.
            };

            return newContext;
        }


        protected override CallbackStrategyResults Load(AjaxControlContext controlContext)
		{
            UpdateGlobalControl(controlContext);

            // Prepare the callback result.
            return GenerateLoadCallbackResults(controlContext);
		}


        protected virtual CallbackStrategyResults GenerateLoadCallbackResults(AjaxControlContext controlContext)
        {
            CallbackStrategyResults callbackResult = GenerateUpdateCallbackResults(controlContext, null);

            if (TargetControl is IAjaxControl)
            {
                TextWriter htmlWriter = new StringWriter();

                if (!controlContext.CallbackArguments.ControlTypeExists)
                {
                    HttpContext.Current.Items["AjaxControlFramework.ControlScriptStrategy.RenderedControlID"] = controlContext.CallbackArguments.ShorthandID;
                    (TargetControl as IAjaxControl).CallbackStrategy.Scripter.GenerateControlScript(ref htmlWriter);
                }

                (TargetControl as IAjaxControl).CallbackStrategy.Scripter.GenerateDynamicCreateScript(ref htmlWriter, false);

                if (callbackResult.ContainsKey("ControlScript") && !String.IsNullOrEmpty((string)callbackResult["ControlScript"]))
                {
                    callbackResult["ControlScript"] = (string)callbackResult["ControlScript"] + htmlWriter.ToString();
                }
                else
                {
                    callbackResult["ControlScript"] = htmlWriter.ToString();
                }

                callbackResult["ClientID"] = TargetControl.ClientID;
            }

            return callbackResult;
        }


        protected override CallbackStrategyResults Update(AjaxControlContext controlContext)
		{
            UpdateControl(controlContext);

            // Prepare the callback result.
            return GenerateUpdateCallbackResults(controlContext);
		}


        protected virtual CallbackStrategyResults GenerateUpdateCallbackResults(AjaxControlContext controlContext)
        {
            CallbackStrategyResults callbackResult = new CallbackStrategyResults();

            callbackResult.Add("State", controlContext.State);
            callbackResult.Add("Html", controlContext.Html);

            List<IAjaxControl> childAjaxControls = AjaxControlUtils.FindChildControlsByType<IAjaxControl>(TargetControl);

            if (childAjaxControls != null && childAjaxControls.Count > 0)
            {
                TextWriter htmlWriter = new StringWriter();

                foreach (IAjaxControl ajaxControl in childAjaxControls)
                {
                    if (!controlContext.CallbackArguments.ControlTypeExists)
                    {
                        HttpContext.Current.Items["AjaxControlFramework.ControlScriptStrategy.RenderedControlID"] = controlContext.CallbackArguments.ShorthandID;
                        ajaxControl.CallbackStrategy.Scripter.GenerateControlScript(ref htmlWriter);
                    }

                    ajaxControl.CallbackStrategy.Scripter.GenerateDynamicCreateScript(ref htmlWriter, true);
                }

                callbackResult["ControlScript"] = htmlWriter.ToString();
            }

            return callbackResult;
        }


        protected virtual CallbackStrategyResults GenerateUpdateCallbackResults(AjaxControlContext controlContext, object methodInvokeResults)
        {
            CallbackStrategyResults callbackResult = new CallbackStrategyResults();

            callbackResult.Add("Result", methodInvokeResults);
            callbackResult.Add("State", controlContext.State);
            callbackResult.Add("Html", controlContext.Html);

            List<IAjaxControl> childAjaxControls = AjaxControlUtils.FindChildControlsByType<IAjaxControl>(TargetControl);

            if (childAjaxControls != null && childAjaxControls.Count > 0)
            {
                TextWriter htmlWriter = new StringWriter();

                foreach (IAjaxControl ajaxControl in childAjaxControls)
                {
                    if (!controlContext.CallbackArguments.ControlTypeExists)
                    {
                        HttpContext.Current.Items["AjaxControlFramework.ControlScriptStrategy.RenderedControlID"] = controlContext.CallbackArguments.ShorthandID;
                        ajaxControl.CallbackStrategy.Scripter.GenerateControlScript(ref htmlWriter);
                    }

                    ajaxControl.CallbackStrategy.Scripter.GenerateDynamicCreateScript(ref htmlWriter, true);
                }

                callbackResult["ControlScript"] = htmlWriter.ToString();
            }

            return callbackResult;
        }


        protected override CallbackStrategyResults GlobalInvoke(AjaxControlContext controlContext)
		{
			// Invoke the desired method against the out-of-process Control.
            object methodInvokeResults;
            bool success = MethodCache.InvokeMethod(controlContext.Control, TargetControlType, controlContext.CallbackArguments.MethodName, out methodInvokeResults, controlContext.CallbackArguments.Parameters, Authenticator);

            // Handle success/failure.


            if (controlContext.CallbackArguments.ReturnHtml || controlContext.CallbackArguments.AutoUpdateHtml)
            {
                UpdateGlobalControl(controlContext);
                return GenerateUpdateCallbackResults(controlContext);
            }

            // Prepare the callback result.
            return GenerateInvokeCallbackResults(controlContext, methodInvokeResults);
		}


        protected override CallbackStrategyResults Invoke(AjaxControlContext controlContext)
		{
			// Invoke the desired method against the Control.
            object methodInvokeResults;
            bool success = MethodCache.InvokeMethod(Control, TargetControlType, controlContext.CallbackArguments.MethodName, out methodInvokeResults, controlContext.CallbackArguments.Parameters, Authenticator);

            // Handle success/failure.



            if (controlContext.CallbackArguments.ReturnHtml || controlContext.CallbackArguments.AutoUpdateHtml)
            {
                UpdateControl(controlContext);
                return GenerateUpdateCallbackResults(controlContext, methodInvokeResults);
            }

            // Prepare the callback result.
            return GenerateInvokeCallbackResults(controlContext, methodInvokeResults);
		}


        protected virtual CallbackStrategyResults GenerateInvokeCallbackResults(AjaxControlContext controlContext, object methodInvokeResults)
        {
            CallbackStrategyResults callbackResult = new CallbackStrategyResults();

            callbackResult.Add("Result", methodInvokeResults);
            callbackResult.Add("State", controlContext.State);

            if (controlContext.CallbackArguments.ReturnHtml || controlContext.CallbackArguments.AutoUpdateHtml)
            {
                callbackResult.Add("Html", controlContext.Html);
            }

            return callbackResult;
        }


        // The purpose of this method is to call all remaining Page Lifecycle events 
        // (PreRender, Render, SaveControlState, SaveViewState, and Unload) in the correct order.
        protected virtual void UpdateControl(AjaxControlContext context)
        {
            Control.IsUpdating = true;

            if (!context.Control.Visible)
            {
                context.Html = String.Empty;
            }
            else
            {
                context.Validators.ForEach(v => typeof(System.Web.UI.WebControls.BaseValidator).GetField("renderUplevel", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(v, true));


                // Render the control manually.
                RenderingHelper.RenderControl(context.Control, null);


                if (AjaxControlHandler.CurrentScriptManager != null)
                {
                    object scriptControlManager = typeof(System.Web.UI.ScriptManager).GetProperty("ScriptControlManager", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetGetMethod(true).Invoke(AjaxControlHandler.CurrentScriptManager, null);
                    ScriptControlManagerType.GetField("_pagePreRenderRaised", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(scriptControlManager, true);

                    // Setting the _pagePreRenderRaised field of the ScriptControlManager ensures that the "Script controls may not be registered before PreRender" error doesn't crop up.
                }

                PreRenderRecursiveInternal(context.Control);


                TextWriter html = new StringWriter();
                context.Control.RenderControl(new HtmlTextWriter(html));

                context.Html = html.ToString();
            }

            // Save the ControlState and ViewState of the Control being updated.
            // *Reminder* - The "TargetControl" property is correctly set as the global control within the LoadGlobalControl method.
            if (Control.StateStrategy != null && context.Control.EnableViewState)
            {
                Control.StateStrategy.Save();
            }

            UnloadRecursive(context.Control, true);

            Control.IsUpdating = false;
        }


        protected virtual void UpdateGlobalControl(AjaxControlContext context)
        {
            // There may be some instances where extenders of the ControlCallbackStrategy class will want to include special logic 
            // with the Updating of a global control. Out-of-the-box the AJAX Control Framework doesn't require any special logic 
            // for global controls, so it will will simply call the "UpdateControl" method.

            UpdateControl(context);
        }
        //------\\ Methods //-----------------------------------------------//
    }
}
